package edu.bbu.cshu.wmpc.checking.similarity.cosine;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import java.util.StringTokenizer;

import edu.bbu.cshu.wmpc.checking.similarity.Similarity;
/**
 * <ul>
 * <li><b><i>CosineSimilarity</i></b></li>
 * <li style="list-style: none;"></li>
 * <li style="list-style: none;">
 * Class that extends {@link Similarity} and implements the {@code cosine similarity} test.
 * 
 * @author WMPC team
 * </li>
 * </ul>
 */ 
public class CosinSimilarity extends Similarity {
	/**
	 * <ul>
	 * <li><b><i>line1</i></b></li>
	 * <li style="list-style: none;"></li>
	 * <li style="list-style: none;">
	 * {@link String} that contains the first {@code String}.
	 * 
	 * </li>
	 * </ul>
	 */
	private String line1;
	/**
	 * <ul>
	 * <li><b><i>line1</i></b></li>
	 * <li style="list-style: none;"></li>
	 * <li style="list-style: none;">
	 * {@link String} that contains the second {@code String}.
	 * 
	 * </li>
	 * </ul>
	 */
	private String line2;
	/**
	 * <ul>
	 * <li><b><i>words</i></b></li>
	 * <li style="list-style: none;"></li>
	 * <li style="list-style: none;">
	 * {@link ArrayList}<{@link String}> that contains a single occurrence of all the words from {@code line1} and {@code line2}.
	 * 
	 * </li>
	 * </ul>
	 */
	private ArrayList<String> words;
	/**
	 * <ul>
	 * <li><b><i>wCount</i></b></li>
	 * <li style="list-style: none;"></li>
	 * <li style="list-style: none;">
	 * The number of occurring {@code words}.
	 * 
	 * </li>
	 * </ul>
	 */
	private int wCount;
	/**
	 * <ul>
	 * <li><b><i>wordCount1</i></b></li>
	 * <li style="list-style: none;"></li>
	 * <li style="list-style: none;">
	 * {@code int Array} that contains the occurrence of each word from the {@link ArrayList}<{@link String}> {@code words} in {@code line1}.
	 * 
	 * </li>
	 * </ul>
	 */
	private int[] wordCount1;
	/**
	 * <ul>
	 * <li><b><i>wordCount1</i></b></li>
	 * <li style="list-style: none;"></li>
	 * <li style="list-style: none;">
	 * {@code int Array} that contains the occurrence of each word from the {@link ArrayList}<{@link String}> {@code words} in {@code line2}.
	 * 
	 * </li>
	 * </ul>
	 */
	private int[] wordCount2;
	/**
	 * <ul style="margin-top: 24px;">
	 * <li><b><i>CosineSimilarity</i></b></li>
	 * <li style="list-style: none;"></li>
	 * <li style="list-style: none;">
	 * <p style="margin-left: 7px;">{@code public CosineSimilarity(double limit)}</p>
	 * <br>	
	 * Constructor that sets the value of {@code limit}.
	 * 
	 * @param limit - the limit at which two lines are considered similar
	 * </li>
	 * </ul>
	 */
	public CosinSimilarity(double limit) {
		this.limit = limit;
	}

	protected void setString(int index, String str) {		
		switch (index) {
			case 1: line1 = ""; break;
			case 2: line2 = ""; break;
		}

		char tmp;
		int length = str.length();
		char[] ch = str.toCharArray();

		for (int i = 0; i < length; i++) {
			tmp = ch[i];

			if (specia.indexOf(tmp) != -1) {
				addToLine(index, tmp);
			} else {
				if (Character.isWhitespace(tmp)) {
					if (i < (length-1)) {
						if (!Character.isWhitespace(ch[i+1])) {
							addToLine(index, tmp);
						}
					}
				} else {
					if (Character.isLetterOrDigit(tmp)) {
						if (i != 0) {
							if (i < (length-1)) {
								if (!Character.isWhitespace(ch[i-1])  && (Character.isLetterOrDigit(ch[i-1]) || (specia.indexOf(ch[i-1]) != -1))) {
									addToLine(index, tmp);
								} else {
									if (!Character.isWhitespace(ch[i+1]) && (Character.isLetterOrDigit(ch[i+1]) || (specia.indexOf(ch[i+1]) != -1))) {
										addToLine(index, tmp);
									}
								}
							} else {
								if (!Character.isWhitespace(ch[i-1])  && (Character.isLetterOrDigit(ch[i-1]) || (specia.indexOf(ch[i-1]) != -1))) {
									addToLine(index, tmp);
								}
							}
						} else {
							if (!Character.isWhitespace(ch[i+1]) && (Character.isLetterOrDigit(ch[i+1]) || (specia.indexOf(ch[i+1]) > -1))) {
								addToLine(index, tmp);
							}
						}
					}
				}
			}
		}
	}
	/**
	 * <ul style="margin-top: 24px;">
	 * <li><b><i>addToLine</i></b></li>
	 * <li style="list-style: none;"></li>
	 * <li style="list-style: none;">
	 * <p style="margin-left: 7px;">{@code private void addToLine(int index, char tmp)}</p>
	 * <br>	
	 * Adds {@code tmp} to {@code line1} or {@code line2} depending on the {@code index}.
	 * 
	 * @param index - specifies to which {@code String} the {@code char} is to be added
	 * @param tmp	- the character to be added
	 * </li>
	 * </ul>
	 */
	private void addToLine(int index, char tmp){
		switch (index) {
			case 1: line1 += "" + Character.toLowerCase(tmp); break;
			case 2: line2 += "" + Character.toLowerCase(tmp); break;
		}
	}
	/**
	 * <ul style="margin-top: 24px;">
	 * <li><b><i>getWords</i></b></li>
	 * <li style="list-style: none;"></li>
	 * <li style="list-style: none;">
	 * <p style="margin-left: 7px;">{@code private void getWords()}</p>
	 * <br>	
	 * Adds all the words from {@code line1} and {@code line2} to the {@link ArrayList}<{@link String}> {@code words}. 
	 * 
	 * </li>
	 * </ul>
	 */
	private void getWords() {
		Set<String> tmp = new HashSet<String>();
		String word;
		
		StringTokenizer st = new StringTokenizer(line1, " ");
		
		while (st.hasMoreTokens()) {
			if ((word = st.nextToken()).length() >= 1) {
				tmp.add(word);
			}
		}
		
		st = new StringTokenizer(line2, " ");
		
		while (st.hasMoreTokens()) {
			if ((word = st.nextToken()).length() >= 1) {
				tmp.add(word);
			}
		}
		
		words = new ArrayList<String>(tmp);
		wCount = words.size();
	}
	/**
	 * <ul style="margin-top: 24px;">
	 * <li><b><i>countWords</i></b></li>
	 * <li style="list-style: none;"></li>
	 * <li style="list-style: none;">
	 * <p style="margin-left: 7px;">{@code private void countWords()}</p>
	 * <br>	
	 * Counts the occurrence of each word from the {@link ArrayList}<{@link String}> {@code words} in {@code line1}, and in {@code line2}.
	 * 
	 * </li>
	 * </ul>
	 */
	private void countWords() {
		wordCount1 = new int[wCount];
		wordCount2 = new int[wCount];
		
		for (int i = 0; i < wCount; i++) {
			wordCount1[i] = countMatching(line1, words.get(i));
			wordCount2[i] = countMatching(line2, words.get(i));
		}
	}
	/**
	 * <ul style="margin-top: 24px;">
	 * <li><b><i>countMatching</i></b></li>
	 * <li style="list-style: none;"></li>
	 * <li style="list-style: none;">
	 * <p style="margin-left: 7px;">{@code private int countMatching({@link String} str, {@link String} word)}</p>
	 * <br>	
	 * Counts the occurrence of {@code word} in {@code str}.
	 * 
	 * @param str  - {@code String} in which to search
	 * @param word - {@code String} which to search for
	 * @return	   the number of occurrence
	 * </li>
	 * </ul>
	 */
	private int countMatching(String str, String word) {
		int count = 0;
		int idx = 0;
		
		while ((idx = str.indexOf(word, idx)) != -1) {
			count++;
			idx += word.length();
		}

		return count;
	}
	
	protected double executeStringTest() {
		getWords();
		countWords();
		
		double dotProduct = 0;
		double magnitude1 = 0;
		double magnitude2 = 0;
		int wc1;
		int wc2;
			
		for (int i = 0; i < wCount; i++) {
			wc1 = wordCount1[i];
			wc2 = wordCount2[i];
			
			dotProduct += wc1 * wc2;
			magnitude1 += wc1 * wc1;
			magnitude2 += wc2 * wc2;
		}

		double result = (double) dotProduct / (Math.sqrt(magnitude1) * Math.sqrt(magnitude2));
		if (Double.isNaN(result)) {
			result = (double) 0;
		}

		return result;
	}

}
